using System;

namespace PropertyExpression.Common
{
    public static class OptionalValue
    {
        public static IOptionalValue<TValue> Nothing<TValue>()
        {
            return new NotExistOptionalValue<TValue>();
        }

        public static IOptionalValue<TValue> AsOptionalValue<TValue>(
            this TValue value)
        {
            return new ExistOptionalValue<TValue>(value);
        }

        public static IOptionalValue<TTarget> ProcessValue<TSource, TTarget>(
            this IOptionalValue<TSource> optionalValue,
            Func<TSource, TTarget> func)
        {
            return optionalValue.Process(
                value => func(value).AsOptionalValue(),
                Nothing<TTarget>
                );
        }

        public static IOptionalValue<TTarget> ProcessValue<TSource, TTarget>(
            this IOptionalValue<TSource> optionalValue,
            Func<TSource, IOptionalValue<TTarget>> func)
        {
            return optionalValue.Process(func, Nothing<TTarget>);
        }

        public static void Process<TValue>(
            this IOptionalValue<TValue> optionalValue, 
            Action<TValue> existAction, 
            Action notExistAction)
        {
            optionalValue.Process(existAction.ToFunc(), notExistAction.ToFunc());
        }

        public static void ProcessValue<TValue>(
            this IOptionalValue<TValue> optionalValue, 
            Action<TValue> existAction)
        {
            Process(optionalValue, existAction, () => { });
        }

        public static bool HasValue<TValue>(
            this IOptionalValue<TValue> optionalValue)
        {
            return optionalValue.Process(delegate { return true; }, () => false);
        }

        public static TValue GetValue<TValue>(
            this IOptionalValue<TValue> optionalValue)
        {
            return optionalValue.Process(
                result => result,
                () => { throw new InvalidOperationException(); }
                );
        }

        public static TValue GetValueOrDefault<TValue>(
            this IOptionalValue<TValue> optionalValue)
        {
            return optionalValue.Process(result => result, () => default(TValue));
        }

        private class NotExistOptionalValue<TValue> : IOptionalValue<TValue>
        {
            public TResult Process<TResult>(
                Func<TValue, TResult> existFunc, Func<TResult> notExistFunc)
            {
                return notExistFunc();
            }
        }

        private class ExistOptionalValue<TValue> : IOptionalValue<TValue>
        {
            private readonly TValue value;

            public ExistOptionalValue(TValue value)
            {
                this.value = value;
            }

            public TResult Process<TResult>(
                Func<TValue, TResult> existFunc, Func<TResult> notExistFunc)
            {
                return existFunc(value);
            }
        }
    }
}